package com.milloc.db.service.impl;

import com.alibaba.druid.pool.DruidDataSource;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.github.pagehelper.PageInterceptor;
import com.milloc.db.common.metadata.MetaDataHelper;
import com.milloc.db.common.metadata.MetaDataInterceptor;
import com.milloc.db.common.metadata.MetaDataResponse;
import com.milloc.db.entity.DataSourceEntity;
import com.milloc.db.entity.ParameterEntity;
import com.milloc.db.entity.SQLEntity;
import com.milloc.db.service.SQLRunnerService;
import com.milloc.db.util.MultiTransactionUtil;
import lombok.SneakyThrows;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * 运行SQL的Service
 *
 * todo 添加测试功能
 * @author gongdeming
 * @create 2019-08-23
 */
@Service
public class SQLRunnerServiceImpl implements SQLRunnerService {
    private static final String DB_PREFIX = "datasource_";
    private static final String SQL_SESSION_PREFIX = "configuration_";
    private static final String MAPPER_NAMESPACE = "mapper";
    private static final String SQL_PREFIX = MAPPER_NAMESPACE + ".sql_";

    private static final Map<String, SoftReference<SqlSessionFactory>> SQL_SESSION_FACTORIES = new HashMap<>();
    private static final Set<String> DATA_SOURCE_RELOAD = new HashSet<>();
    private static final Set<String> MAPPED_STATEMENT_RELOAD = new HashSet<>();

    private static final PageInterceptor PAGE_INTERCEPTOR = new PageInterceptor();
    private static final MetaDataInterceptor META_DATA_INTERCEPTOR = new MetaDataInterceptor();
    private static final TransactionFactory TRANSACTION_FACTORY = new JdbcTransactionFactory();

    @Override
    public Object runSQL(SQLEntity sqlEntity, Map<String, Object> params) {
        String sqlId = SQL_PREFIX + sqlEntity.getId();
        SqlSession sqlSession = getSqlSession(sqlEntity);
        try {
            switch (sqlEntity.getCommandType()) {
                case SELECT:
                    if (sqlEntity.isPage()) {
                        int pageSize = (int) params.getOrDefault("pageSize", 10);
                        int pageNum = (int) params.getOrDefault("pageNum", 1);
                        PageHelper.startPage(pageNum, pageSize);
                        List<Map<String, Object>> res = sqlSession.selectList(sqlId, params);
                        return PageInfo.of(res);
                    }
                    return sqlSession.selectList(sqlId, params);
                case DELETE:
                    return sqlSession.delete(sqlId, params);
                case INSERT:
                    return sqlSession.insert(sqlId, params);
                case UPDATE:
                    return sqlSession.update(sqlId, params);
                default:
                    throw new IllegalArgumentException("错误的sql类型");
            }
        } finally {
            closeSession(sqlSession);
        }
    }

    @Override
    public MetaDataResponse<?> runSQLWithMetaData(SQLEntity sqlEntity, Map<String, Object> params) {
        MetaDataHelper.start();
        Object o = runSQL(sqlEntity, params);
        List<ParameterEntity> parameter = MetaDataHelper.getParameter();
        return new MetaDataResponse<>(parameter, o);
    }

    @Override
    public void reloadDataSource(String dataSourceEntityId) {
        DATA_SOURCE_RELOAD.add(dataSourceEntityId);
    }

    @Override
    public void reloadSQL(String sqlEntityId) {
        MAPPED_STATEMENT_RELOAD.add(sqlEntityId);
    }

    /**
     * 获取SqlSessionFactory
     * <p>
     * 每个factory对应一个configuration，对应一个environment，对应一个datasource，对应多个sql
     *
     * @param sqlEntity sqlEntity
     * @return SqlSessionFactory
     */
    private synchronized SqlSessionFactory getSessionFactory(SQLEntity sqlEntity) {
        DataSourceEntity dataSourceEntity = sqlEntity.getDataSourceEntity();
        String sessionFactoryId = SQL_SESSION_PREFIX + dataSourceEntity.getId();
        if (Objects.isNull(SQL_SESSION_FACTORIES.get(sessionFactoryId))) {
            Configuration configuration = new Configuration();
            configuration.addInterceptor(PAGE_INTERCEPTOR);
            configuration.addInterceptor(META_DATA_INTERCEPTOR);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
            SQL_SESSION_FACTORIES.put(sessionFactoryId, new SoftReference<>(sqlSessionFactory));
        }

        SqlSessionFactory sqlSessionFactory = Optional.of(SQL_SESSION_FACTORIES.get(sessionFactoryId))
                .map(SoftReference::get)
                .orElseThrow(NullPointerException::new);
        Configuration configuration = Objects.requireNonNull(sqlSessionFactory.getConfiguration());
        // 设置或者更新environment
        String dataSourceId = DB_PREFIX + dataSourceEntity.getId();
        if (Objects.isNull(configuration.getEnvironment()) || DATA_SOURCE_RELOAD.contains(dataSourceId)) {
            Environment environment = getEnvironment(dataSourceEntity);
            configuration.setEnvironment(environment);
            DATA_SOURCE_RELOAD.remove(dataSourceId);
        }
        // 设置或更新mappedStatement
        String sqlName = SQL_PREFIX + sqlEntity.getId();
        if (!configuration.hasStatement(sqlName) || MAPPED_STATEMENT_RELOAD.contains(sqlName)) {
            initMappedStatement(sqlEntity, configuration);
            MAPPED_STATEMENT_RELOAD.remove(sqlName);
        }
        return sqlSessionFactory;
    }

    /**
     * 根据properties加载DruidDataSource，以生成environment
     *
     * @param entity dataSourceEntity
     * @return environment
     */
    @SneakyThrows
    private Environment getEnvironment(DataSourceEntity entity) {
        String environmentId = DB_PREFIX + entity.getId();
        String entityProperties = entity.getProperties();
        Objects.requireNonNull(entityProperties);
        try (InputStream reader = new ByteArrayInputStream(entityProperties.getBytes())) {
            Properties properties = new Properties();
            properties.load(reader);
            Properties druidProperties = new Properties();
            properties.forEach((k, v) -> druidProperties.setProperty("druid." + k, (String) v));

            DruidDataSource druidDataSource = new DruidDataSource();
            druidDataSource.configFromPropety(druidProperties);
            druidDataSource.setFilters("stat");
            // TODO 连接池配置
            return new Environment(environmentId, TRANSACTION_FACTORY, druidDataSource);
        }
    }

    /**
     * 利用xml加载sql
     *
     * @param entity        sqlEntity
     * @param configuration 需要加载到的配置对象
     */
    @SneakyThrows
    private void initMappedStatement(SQLEntity entity, Configuration configuration) {
        String sqlName = SQL_PREFIX + entity.getId();
        String sql = entity.getSql();
        String tagName = entity.getCommandType().name().toLowerCase();
        String xml = String.format("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" +
                        "<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" " +
                        "\"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">" +
                        "<mapper namespace=\"%s\"><%s id=\"%s\" %s>%s</%s></mapper>",
                MAPPER_NAMESPACE, tagName, sqlName, entity.getCommandType() == SqlCommandType.SELECT ?
                        "resultType=\"hashMap\"" : "",
                sql, tagName);
        try (InputStream inputStream = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) {
            new XMLMapperBuilder(inputStream, configuration, sqlName, Collections.emptyMap()).parse();
        }
    }

    /**
     * 通过多数据源事务工具，获取sqlSession
     *
     * @param sqlEntity sqlEntity
     * @return 获取sqlSession
     */
    private SqlSession getSqlSession(SQLEntity sqlEntity) {
        SqlSessionFactory sessionFactory = getSessionFactory(sqlEntity);
        return MultiTransactionUtil.getSqlSession(sessionFactory);
    }

    private void closeSession(SqlSession sqlSession) {
        MultiTransactionUtil.closeSqlSession(sqlSession);
    }
}
